package net.ufrog.common;

import net.ufrog.common.exception.ServiceException;
import net.ufrog.common.utils.Files;
import net.ufrog.common.utils.Objects;
import net.ufrog.common.utils.Strings;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.*;
import javax.mail.Message.RecipientType;
import javax.mail.internet.*;
import javax.mail.util.ByteArrayDataSource;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Stream;

/**
 * 邮件寄送器
 * 
 * @author ultrafrog
 * @version 1.0, 2014-02-07
 * @since 1.0
 */
public class Mailer {

    private static final String PROP_SMTP_HOST		= "mail.smtp.host";
    private static final String PROP_SMTP_PORT		= "mail.smtp.port";
    private static final String PROP_SMTP_AUTH		= "mail.smtp.auth";

    /** 消息 */
    private Message message;

    /** 构造函数 */
    public Mailer() {
        String host = CoreConfig.getMailSmtpHost();
        String port = CoreConfig.getMailSmtpPort();
        String username = CoreConfig.getMailSmtpUsername();
        String password = CoreConfig.getMailSmtpPassword();
        String from = CoreConfig.getMailFrom();
        String fromName = CoreConfig.getMailFromName();
        initialize(host, port, username, password, from, fromName);
    }

    /**
     * 构造函数
     *
     * @param host 地址
     * @param port 端口
     * @param username 用户名
     * @param password 密码
     * @param from 发送地址
     * @param fromName 发送名称
     */
    public Mailer(String host, String port, String username, String password, String from, String fromName) {
        initialize(host, port, username, password, from, fromName);
    }

    /**
     * 添加收件地址
     *
     * @param address 收件地址
     */
    public void addTo(String address) {
        try {
            message.addRecipient(RecipientType.TO, new InternetAddress(address.trim()));
        } catch (MessagingException e) {
            throw new ServiceException("cannot add address '" + address + "'.", "exception.mail.to", e);
        }
    }

    /**
     * 添加抄送地址
     *
     * @param address 抄送地址
     */
    public void addCc(String address) {
        try {
            message.addRecipient(RecipientType.CC, new InternetAddress(address.trim()));
        } catch (MessagingException e) {
            throw new ServiceException("cannot add address '" + address + "'.", "exception.mail.cc", e);
        }
    }

    /**
     * 添加暗送地址
     *
     * @param address 暗送地址
     */
    public void addBcc(String address) {
        try {
            message.addRecipient(RecipientType.BCC, new InternetAddress(address.trim()));
        } catch (MessagingException e) {
            throw new ServiceException("cannot add address '" + address + "'.", "exception.mail.bcc", e);
        }
    }

    /**
     * 设置主题
     *
     * @param subject 主题
     */
    public void setSubject(String subject) {
        try {
            message.setSubject(subject);
        } catch (MessagingException e) {
            throw new ServiceException("cannot set subject '" + subject + "'.", "exception.mail.subject", e);
        }
    }

    /**
     * 设置纯文本内容
     *
     * @param text 纯文本内容
     */
    public void setText(String text) {
        try {
            message.setText(text);
        } catch (MessagingException e) {
            throw new ServiceException("cannot set text '" + text + "'.", "exception.mail.content", e);
        }
    }

    /**
     * 设置超文本内容
     *
     * @param text 超文本内容
     * @param attachments 附件
     */
    public void setHtml(String text, Map<String, InputStream> attachments) {
        try {
            Multipart multipart = new MimeMultipart();
            BodyPart html = new MimeBodyPart();
            html.setContent(text, "text/html; charset=utf-8");
            multipart.addBodyPart(html);

            if (attachments != null) {
                attachments.forEach((k, v) -> {
                    try {
                        BodyPart attach = new MimeBodyPart();
                        DataSource dataSource = new ByteArrayDataSource(v, Files.getMimeType(k));
                        attach.setFileName(k);
                        attach.setDataHandler(new DataHandler(dataSource));
                        multipart.addBodyPart(attach);
                    } catch (MessagingException e) {
                        throw new ServiceException("cannot set attach '" + k + "'.", "exception.mail.content", e);
                    } catch (IOException e) {
                        throw new ServiceException(e.getMessage(), e);
                    }
                });
            }
            message.setContent(multipart);
        } catch (MessagingException e) {
            throw new ServiceException("cannot set html '" + text + "'.", "exception.mail.content", e);
        }
    }

    /**
     * 设置超文本内容
     *
     * @param text 超文本内容
     * @param attachments 附件
     */
    public void setHtml(String text, Object... attachments) {
        Map<String, InputStream> mAttach = Objects.map(InputStream.class, attachments);
        setHtml(text, mAttach);
    }

    /** 发送邮件 */
    public void send() {
        ThreadPools.execute(new SendTask());
    }

    /**
     * 初始化
     *
     * @param host 地址
     * @param port 端口
     * @param username 用户名
     * @param password 密码
     * @param from 发送地址
     * @param fromName 发送名称
     */
    private void initialize(String host, String port, String username, String password, String from, String fromName) {
        Properties props = new Properties();
        Authenticator authenticator = Strings.empty(username) ? null : new Authenticator(username, password);
        props.put(PROP_SMTP_HOST, host);
        props.put(PROP_SMTP_PORT, port);
        props.put(PROP_SMTP_AUTH, String.valueOf(!Strings.empty(username)));

        message = new MimeMessage(Session.getDefaultInstance(props, authenticator));
        try {
            InternetAddress internetAddress = Strings.empty(fromName) ? new InternetAddress(from) : new InternetAddress(from, MimeUtility.encodeText(fromName), "utf-8");
            message.setFrom(internetAddress);
        } catch (MessagingException | UnsupportedEncodingException e) {
            throw new ServiceException("cannot set address '" + from + "'.", "exception.mail.from", e);
        }
        Logger.debug("initialize mail host: %s, port: %s, username: %s, from: %s", host, port, username, from);
    }

    /**
     * 发送纯文本邮件
     *
     * @param subject 主题
     * @param text 纯文本内容
     * @param addresses 地址数组
     */
    public static void sendText(String subject, String text, String... addresses) {
        Mailer mailer = new Mailer();
        Stream.of(addresses).forEach(mailer::addTo);
        mailer.setSubject(subject);
        mailer.setText(text);
        mailer.send();
    }

    /**
     * 发送超文本邮件
     *
     * @param subject 主题
     * @param html 超文本内容
     * @param addresses 地址数组
     */
    public static void sendHtml(String subject, String html, String... addresses) {
        Mailer mailer = new Mailer();
        Stream.of(addresses).forEach(mailer::addTo);
        mailer.setSubject(subject);
        mailer.setHtml(html);
        mailer.send();
    }

    /**
     * 认证器
     *
     * @author ultrafrog
     * @version 1.0, 2014-02-07
     * @since 1.0
     */
    protected class Authenticator extends javax.mail.Authenticator {

        /** 用户名 */
        private String username;

        /** 密码 */
        private String password;

        /**
         * 构造函数
         *
         * @param username 用户名
         * @param password 密码
         */
        Authenticator(String username, String password) {
            this.username = username;
            this.password = password;
        }

        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication(username, password);
        }
    }

    /**
     * 发送任务
     *
     * @author ultrafrog
     * @version 1.0, 2014-03-13
     * @since 1.0
     */
    private class SendTask implements Runnable {

        @Override
        public void run() {
            try {
                message.setSentDate(new Date());
                Transport.send(message);
                if (Logger.isInfoEnabled()) Logger.info("send mail '%s' to '%s' success.", message.getSubject(), InternetAddress.toString(message.getAllRecipients()));
            } catch (MessagingException e) {
                Logger.error(e.getMessage());
                throw new ServiceException(e.getMessage(), e);
            }
        }
    }
}
